import {
  ChangeDetectionStrategy,
  Component,
  EventEmitter,
  Input,
  OnInit,
  Output,
  ViewChild,
  ViewEncapsulation,
  forwardRef,
} from '@angular/core';
import { ENTER } from '@angular/cdk/keycodes';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject, skip } from 'rxjs';
import { LvSizeLD } from '../core/types';
import { InputDirective } from '../input/input.directive';
import { TagItem } from '../tag/interface';
import { InputBoolean, InputNumber, TypeUtils } from '../core/api';
import { InputTagItem, InputTagProps } from './interface';

export const InputTagHostConfig = {
  class: 'lv-input-tag',
  '[class.lv-input-tag-large]': 'lvSize === "large"',
  '[class.lv-input-tag-focused]': 'focused',
  '[class.lv-input-tag-disabled]': 'lvDisabled',
  '(mousedown)': 'handleMouseDown($event)',
  '(click)': 'handleClick($event)',
};

@Component({
  selector: 'lv-input-tag',
  templateUrl: './input-tag.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: InputTagHostConfig,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof InputTagComponent => InputTagComponent),
      multi: true,
    },
  ],
})
export class InputTagComponent implements InputTagProps, ControlValueAccessor, OnInit {
  @Input() lvSeparator: string[];
  @Input() lvPlaceholder: string = '';
  @Input() lvSize: LvSizeLD = 'default';
  @Input() @InputNumber() lvMaxLength: number;
  @Input() lvClearIcon: string | boolean = false;
  @Output() lvOnBlur = new EventEmitter<Event>();
  @Output() lvOnClear = new EventEmitter<void>();
  @Output() lvOnFocus = new EventEmitter<Event>();
  @ViewChild('inputRef') inputRef: InputDirective;
  @Input() lvOnPressEnter: (e: KeyboardEvent) => void;
  @Input() @InputBoolean() lvDisabled: boolean = false;
  @Input() @InputBoolean() lvAutoFocus: boolean = false;
  @Input() @InputBoolean() lvSaveOnBlur: boolean = false;
  @Output() lvOnInputChange = new EventEmitter<string>();
  @Output() lvOnKeyDown = new EventEmitter<KeyboardEvent>();
  @Input() lvOnRemove: (value: Array<InputTagItem | string>) => boolean | void;
  @Output() lvOnChange = new EventEmitter<{ value: string[]; tagValue: any }>();
  focused: boolean;
  inputValue: string;
  tagValue: TagItem[];
  typeUtils = TypeUtils;
  motionDone$ = new Subject<void>();
  value: Array<InputTagItem | string> = [];
  constructor() {}

  registerOnTouched(): void {}

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  handleClick(): void {
    if (!this.focused) {
      this.focus();
    }
  }

  handleFocus(e: Event): void {
    this.focused = true;
    this.lvOnFocus?.emit(e);
  }

  // =========================== register ControlValueAccessor ===========================
  writeValue(value: Array<InputTagItem | string>): void {
    this.value = value || [];
    this.genTagItem();
  }

  setInputValue(curValue: string): void {
    this.inputValue = curValue;
    this.lvOnInputChange?.emit(curValue);
  }

  // =========================== interaction event ===========================
  handleMouseDown(e: Event): void {
    if (this.focused && (e.target as any).tagName !== 'INPUT') {
      e?.preventDefault();
    }
  }

  handleBlur(e: Event): void {
    this.focused = false;
    this.lvOnBlur?.emit(e);
    if (this.lvSaveOnBlur) {
      this.handleAdd(this.inputValue);
    }
  }

  // =========================== clear ===========================
  handleClear(payload?: string | InputTagItem): void {
    const newValue = this.clearValue(payload);
    this.clearInputValue();
    if (newValue) {
      this.lvOnChange?.emit({ value: this.getTagValue(newValue), tagValue: newValue });
    }
  }

  handleKeyDown(e: KeyboardEvent): void {
    this.lvOnKeyDown?.emit(e);
    if (this.lvOnPressEnter) {
      this.lvOnPressEnter(e);
      return;
    }
    // 默认按回车时添加标签
    if (e.keyCode === ENTER) {
      this.handleAdd(this.inputValue);
    }
  }

  ngOnInit(): void {
    if (this.lvAutoFocus && !this.lvDisabled) {
      const motionDone$ = this.motionDone$.pipe(skip(1)).subscribe((): void => {
        this.focus();
        motionDone$.unsubscribe();
        this.motionDone$.complete();
      });
    }
    this.writeValue([]);
  }

  private onChange: any = (): void => {};

  // =========================== add ===========================
  private handleAdd = (payload: string): void => {
    const newValue = this.addValue(payload);
    this.clearInputValue();
    if (!newValue) {
      return;
    }
    this.lvOnChange?.emit({ value: this.getTagValue(newValue), tagValue: newValue });
  };

  // =========================== input value ===========================
  private clearInputValue(): void {
    this.inputValue = '';
    this.lvOnInputChange?.emit('');
  }

  // =========================== utils ===========================
  private getTagValue(data: any): any {
    return data?.map((item): string => this.getValueString(item));
  }

  private getValueDisabled(data: string | InputTagItem): boolean {
    return TypeUtils.isObject<InputTagItem>(data) && data.disabled;
  }

  private getValueString(data: string | InputTagItem): string {
    return TypeUtils.isObject<InputTagItem>(data) ? data.value : data;
  }

  private focus(e?: Event): void {
    if (!this.focused) {
      this.inputRef.elementRef.nativeElement.focus();
    }
    e?.stopPropagation();
  }

  private genTagItem(): void {
    this.tagValue = this.value.map((item): { removable: true; disabled: boolean; label: string } => ({
      label: this.getValueString(item),
      removable: true,
      disabled: this.getValueDisabled(item) || this.lvDisabled,
    }));
  }

  private addValue(payload?: string): any {
    // 拒绝空标签和重复标签
    if (!payload || this.value.find((item): boolean => this.getValueString(item) === payload)) {
      return false;
    }
    const tagValue = this.getTagValue(this.value);
    const separatorValue = payload
      .split(new RegExp(`[${this.lvSeparator?.join('') || ''}]`))
      .filter((item): string => !tagValue.includes(item) && item);
    const newValue = [...this.value, ...new Set(separatorValue)];
    this.value = newValue;
    this.genTagItem();
    this.onChange(this.value);
    return newValue;
  }

  private clearValue(payload?: string | InputTagItem): any {
    // 排除disabled项
    const newValue =
      this.value.filter(
        (item): boolean => this.getValueDisabled(item) || (payload && this.getValueString(item) !== payload),
      ) || [];
    const tagValue = new Set(this.getTagValue(newValue));
    const clearedValue = this.value.filter((item): boolean => !tagValue.has(this.getValueString(item))) || [];
    const isRemove = this.lvOnRemove?.(payload ? [payload] : clearedValue);
    if (isRemove === false) {
      return false;
    }
    this.value = newValue;
    this.genTagItem();
    this.onChange(this.value);
    if (!payload) {
      this.lvOnClear?.emit();
    }
    return newValue;
  }
}
